feat: switch base image to GitHub runner and add DevOps tools#1
feat: switch base image to GitHub runner and add DevOps tools#1miragecentury wants to merge 6 commits intomainfrom
Conversation
Replace ubuntu:24.04 base with ghcr.io/actions/runner for native GitHub Actions runner support. Add skopeo, Argo Workflows CLI, HashiCorp Packer, and Cloud Native Buildpacks (pack) CLI as configurable build args. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W
Swap out HashiCorp Packer for Kargo CLI (v1.9.2) for application lifecycle orchestration support. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W
Runs weekly (Monday 08:00 UTC) and on manual dispatch. Checks latest releases for Argo, Kargo, and pack CLIs and opens a PR when updates are available. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W
- Add build tools to image (dive, trivy, buildah, yq, hadolint) so the image can build itself as a self-hosted runner - Configure buildah vfs storage driver for container/rootless usage - Create semantic-release config for automated versioning from conventional commits with changelog generation - Add release workflow: semantic-release -> buildah build -> dive filesystem scan -> trivy vulnerability scan -> skopeo push with semver tags (major, major.minor, full, latest) - Add CI workflow: commitlint, hadolint lint, and build test on PRs - Update scheduled update-tools workflow with new tools (dive, hadolint, yq) - Add best practice configs: .hadolint.yaml (trusted registries), .commitlintrc.yaml (conventional commits), .containerignore (minimal build context) https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W
Install pre-commit in the container image and add .pre-commit-config.yaml with hooks for trailing whitespace, YAML validation, hadolint, shellcheck, and commitlint. https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W
There was a problem hiding this comment.
Pull request overview
This pull request transitions the container image from a basic Ubuntu base to the official GitHub Actions runner image, adding comprehensive DevOps tooling to create a self-hosted runner capable of building itself. The changes establish a complete CI/CD pipeline with automated version management, security scanning, and tool updates.
Changes:
- Replaced
ubuntu:24.04base image withghcr.io/actions/runnerfor native GitHub Actions support - Added DevOps CLI tools (Argo Workflows, Kargo, pack, skopeo) and build pipeline tools (buildah, dive, trivy, hadolint, yq, pre-commit)
- Implemented automated release workflow with semantic versioning, vulnerability scanning, and multi-tag publishing to GHCR
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| Containerfile | Switched to GitHub runner base image; added skopeo, buildah, trivy, dive, hadolint, yq, Argo CLI, Kargo CLI, pack CLI, and pre-commit; added Python 3.12/3.13/3.14 with Poetry and UV |
| manifest.yaml | Updated build args from Ubuntu-specific to tool version args (RUNNER_VERSION, ARGO_VERSION, KARGO_VERSION, PACK_VERSION, DIVE_VERSION, HADOLINT_VERSION, YQ_VERSION) |
| README.md | Complete documentation rewrite covering included tools, CI/CD workflows, release process, local development setup, and project structure |
| .github/workflows/release.yaml | New semantic release workflow with buildah build, hadolint/dive/trivy scanning, and multi-tag push to GHCR |
| .github/workflows/ci.yaml | New CI workflow with commitlint, hadolint linting, and test build validation |
| .github/workflows/update-tools.yaml | New automated tool version checker that creates PRs when updates are available |
| .releaserc.yaml | Semantic release configuration for automated versioning and changelog generation |
| .pre-commit-config.yaml | Pre-commit hooks for code quality, linting, and commit message validation |
| .hadolint.yaml | Hadolint configuration defining trusted container registries |
| .commitlintrc.yaml | Commitlint configuration extending conventional commit rules |
| .containerignore | Build context exclusions for CI artifacts and configuration files |
Comments suppressed due to low confidence (1)
Containerfile:114
- Poetry and UV are installed twice - once in the base stage as root (lines 105-114) and again in the runtime stage as the runner user (lines 127-136). This duplication is wasteful and increases image size unnecessarily. The base stage installations as root won't be available to the runner user anyway. Consider removing lines 105-114 from the base stage since they are properly installed in the runtime stage where they will actually be used.
# Install Poetry latest version and add it to PATH
# hadolint ignore=DL4006
RUN curl -sSL https://install.python-poetry.org | python3 -
# Install UV
# hadolint ignore=DL4006
RUN curl -LsSf https://astral.sh/uv/install.sh | sh
# Add Poetry and UV to PATH
RUN echo "export PATH=\"${APP_HOME}/.local/bin:\$PATH\"" >> ~/.bashrc
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| | Tool | Version | | ||
| |------|---------| | ||
| | Python | 3.12, 3.13, 3.14 (via deadsnakes PPA) | |
There was a problem hiding this comment.
The documentation states that Python 3.14 is installed, but Python 3.14 has not been released yet (as of February 2026, Python 3.14 is still in development). The deadsnakes PPA may not have a stable Python 3.14 release available, which could cause the build to fail. Verify that Python 3.14 is actually available from the deadsnakes PPA, or update the documentation and Containerfile to remove this version until it's officially released.
| | Python | 3.12, 3.13, 3.14 (via deadsnakes PPA) | | |
| | Python | 3.12, 3.13 (via deadsnakes PPA) | |
| docker load -i "build/${IMAGE_NAME}.tar" 2>/dev/null || true | ||
| docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" 2>/dev/null || true |
There was a problem hiding this comment.
The dive scan expects the image to be loaded into the Docker daemon, but the load command on line 98 uses 2>/dev/null || true which silently ignores all errors. If the OCI archive is corrupted or incompatible, the load will fail silently, and the subsequent docker tag on line 99 will also fail silently. This means the dive scan on line 104 will fail with a confusing error about the image not being found. Remove the || true or add explicit error checking to ensure the image is successfully loaded before attempting to scan it.
| docker load -i "build/${IMAGE_NAME}.tar" 2>/dev/null || true | |
| docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" 2>/dev/null || true | |
| docker load -i "build/${IMAGE_NAME}.tar" | |
| docker tag "$(docker images -q | head -1)" "${IMAGE_NAME}:${IMAGE_VERSION}" |
| - name: Validate Containerfile | ||
| run: | | ||
| docker pull -q ghcr.io/hadolint/hadolint:latest | ||
| docker run --rm -i hadolint/hadolint:latest < Containerfile |
There was a problem hiding this comment.
The hadolint validation step doesn't use the .hadolint.yaml configuration file that exists in the repository. The CI workflow (ci.yaml) uses hadolint/hadolint-action@v3.1.0 which automatically picks up the config, but this step uses a plain Docker run command without specifying the config file. Add --config .hadolint.yaml to the docker run command or mount the config file to ensure consistent linting behavior between CI and release workflows.
| docker run --rm -i hadolint/hadolint:latest < Containerfile | |
| docker run --rm -i -v "${PWD}/.hadolint.yaml:/.hadolint.yaml" hadolint/hadolint:latest --config /.hadolint.yaml < Containerfile |
| # Install skopeo | ||
| # hadolint ignore=DL3008 | ||
| RUN apt-get update \ | ||
| && apt-get install --no-install-recommends -y skopeo \ | ||
| && apt-get clean \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
||
| # Install buildah | ||
| # hadolint ignore=DL3008 | ||
| RUN apt-get update \ | ||
| && apt-get install --no-install-recommends -y buildah \ | ||
| && apt-get clean \ | ||
| && rm -rf /var/lib/apt/lists/* | ||
|
|
There was a problem hiding this comment.
Multiple consecutive RUN commands for skopeo (lines 28-33) and buildah (lines 35-40) each perform apt-get update separately. This creates unnecessary image layers and increases build time. Consider combining these apt-get installations into a single RUN command to reduce layers and improve build efficiency. This would also align with Docker best practices for minimizing layer count.
| # Install skopeo | |
| # hadolint ignore=DL3008 | |
| RUN apt-get update \ | |
| && apt-get install --no-install-recommends -y skopeo \ | |
| && apt-get clean \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Install buildah | |
| # hadolint ignore=DL3008 | |
| RUN apt-get update \ | |
| && apt-get install --no-install-recommends -y buildah \ | |
| && apt-get clean \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Install skopeo and buildah | |
| # hadolint ignore=DL3008 | |
| RUN apt-get update \ | |
| && apt-get install --no-install-recommends -y skopeo buildah \ | |
| && apt-get clean \ | |
| && rm -rf /var/lib/apt/lists/* | |
| # Configure buildah storage for container/rootless usage | |
| RUN mkdir -p /etc/containers \ | |
| && printf '[storage]\ndriver = "vfs"\n' > /etc/containers/storage.conf | |
| # Install trivy (vulnerability scanner) | |
| # hadolint ignore=DL3008,DL4006 |
| - name: Build image | ||
| run: docker build -f Containerfile -t test-build . |
There was a problem hiding this comment.
The CI workflow's test build uses docker build which doesn't pass any of the build args defined in manifest.yaml (RUNNER_VERSION, ARGO_VERSION, etc.). This means the test build won't accurately reflect the actual build process used in the release workflow, potentially missing build failures related to these arguments. Consider using the same build approach as release.yaml or at least passing the build args from manifest.yaml.
| - name: Build image | |
| run: docker build -f Containerfile -t test-build . | |
| - name: Set up Docker Buildx | |
| uses: docker/setup-buildx-action@v3 | |
| - name: Build image using manifest | |
| run: docker buildx bake -f manifest.yaml --set *.tags=test-build |
| rev: v2.12.0 | ||
| hooks: | ||
| - id: hadolint-docker | ||
| entry: hadolint/hadolint hadolint |
There was a problem hiding this comment.
The hadolint-docker hook uses a custom entry that runs the hadolint container, but this approach may not work correctly with pre-commit's hook execution model. The entry hadolint/hadolint hadolint appears to be malformed - it should either use the hadolint Docker image properly or use the hadolint binary directly. Consider using the standard hadolint hook configuration or verify this custom entry works as intended in local testing.
| entry: hadolint/hadolint hadolint |
| MAJOR_MINOR="${IMAGE_VERSION%.*}" | ||
| MAJOR="${IMAGE_VERSION%%.*}" | ||
|
|
There was a problem hiding this comment.
The version parsing logic assumes semantic versioning with at least two dots (MAJOR.MINOR.PATCH format). If the release version is "1.0" instead of "1.0.0", the MAJOR_MINOR variable on line 126 will be "1" (same as MAJOR), and MAJOR on line 127 will be empty, causing the subsequent skopeo copy commands to fail or push to incorrect tags. Add validation to ensure the version has the expected format, or make the parsing more robust to handle version strings with fewer components.
| MAJOR_MINOR="${IMAGE_VERSION%.*}" | |
| MAJOR="${IMAGE_VERSION%%.*}" | |
| IFS='.' read -r MAJOR MINOR PATCH <<< "$IMAGE_VERSION" | |
| if [ -z "$MAJOR" ]; then | |
| echo "ERROR: Failed to parse IMAGE_VERSION '$IMAGE_VERSION' into a major version component." >&2 | |
| exit 1 | |
| fi | |
| if [ -n "$MINOR" ]; then | |
| MAJOR_MINOR="${MAJOR}.${MINOR}" | |
| else | |
| MAJOR_MINOR="${MAJOR}" | |
| fi |
| @@ -1,20 +1,10 @@ | |||
| ARG UBUNTU_VERSION=24.04 | |||
| ARG RUNNER_VERSION=latest | |||
There was a problem hiding this comment.
Using RUNNER_VERSION=latest as the default base image version introduces unpredictability and potential breaking changes. The GitHub Actions runner image could be updated at any time with breaking changes, causing builds to fail unexpectedly. Consider pinning to a specific version tag (e.g., a specific release version) to ensure reproducible builds, and update it through the automated tool version update workflow.
| ARG RUNNER_VERSION=latest | |
| ARG RUNNER_VERSION=2.319.1 |
| args: | ||
| - APP_UID=1000 | ||
| - UBUNTU_VERSION=24.04 | ||
| - RUNNER_VERSION=latest |
There was a problem hiding this comment.
The manifest.yaml defines RUNNER_VERSION=latest but the update-tools.yaml workflow doesn't check for updates to the GitHub Actions runner version. Unlike the other tools (argo, kargo, pack, dive, hadolint, yq), there's no automated check or update mechanism for the runner base image version. Consider adding runner version checks to the update workflow to keep the base image up to date.
| - RUNNER_VERSION=latest | |
| - RUNNER_VERSION=2.321.0 |
Replace ubuntu:24.04 base with ghcr.io/actions/runner for native
GitHub Actions runner support. Add skopeo, Argo Workflows CLI,
HashiCorp Packer, and Cloud Native Buildpacks (pack) CLI as
configurable build args.
https://claude.ai/code/session_01RofXXAMZxK4irobNYjYn3W